// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#ifndef MUTEX_H_
#define MUTEX_H_

#include <pthread.h>

#include <cassert>

#include "netlib/base/current_thread.h"
#include "netlib/base/noncopyable.h"

#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif

#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x))

#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)

#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))

#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))

#define ACQUIRED_BEFORE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))

#define ACQUIRED_AFTER(...) THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))

#define REQUIRES(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))

#define REQUIRES_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))

#define ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))

#define ACQUIRE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))

#define RELEASE(...) THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))

#define RELEASE_SHARED(...) THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))

#define TRY_ACQUIRE(...) THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))

#define TRY_ACQUIRE_SHARED(...) \
	THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))

#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))

#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))

#define ASSERT_SHARED_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))

#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))

#define NO_THREAD_SAFETY_ANALYSIS THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)

// End of thread safety annotations }

#ifdef CHECK_PTHREAD_RETURN_VALUE

#ifdef NDEBUG
__BEGIN_DECLS
extern void
__assert_perror_fail(int errnum, const char* file, unsigned int line, const char* function) noexcept
    __attribute__((__noreturn__));
__END_DECLS
#endif

#define MCHECK(ret)                                                     \
	({                                                                  \
		__typeof__(ret) errnum = (ret);                                 \
		if (__builtin_expect(errnum != 0, 0))                           \
			__assert_perror_fail(errnum, __FILE__, __LINE__, __func__); \
	})

#else // CHECK_PTHREAD_RETURN_VALUE

#define MCHECK(ret)                     \
	({                                  \
		__typeof__(ret) errnum = (ret); \
		assert(errnum == 0);            \
		(void)errnum;                   \
	})

#endif // CHECK_PTHREAD_RETURN_VALUE

namespace netlib {

// Use as data member of a class, eg.
//
// class Foo
// {
//  public:
//   int size() const;
//
//  private:
//   mutable MutexLock mutex_;
//   std::vector<int> data_ GUARDED_BY(mutex_);
// };
class CAPABILITY("mutex") MutexLock : NonCopyable {
public:
	MutexLock() { MCHECK(pthread_mutex_init(&mutex_, nullptr)); }

	~MutexLock() {
		assert(holder_ == 0);
		MCHECK(pthread_mutex_destroy(&mutex_));
	}

	// must be called when locked, i.e. for assertion
	bool IsLockedByThisThread() const { return holder_ == current_thread::Tid(); }

	void AssertLocked() const ASSERT_CAPABILITY(this) { assert(IsLockedByThisThread()); }

	// internal usage

	void Lock() ACQUIRE() {
		MCHECK(pthread_mutex_lock(&mutex_));
		AssignHolder();
	}

	void Unlock() RELEASE() {
		UnassignHolder();
		MCHECK(pthread_mutex_unlock(&mutex_));
	}

	pthread_mutex_t* GetPthreadMutex() { return &mutex_; }

private:
	friend class Condition;

	class UnassignGuard : NonCopyable {
	public:
		explicit UnassignGuard(MutexLock& owner) : owner_(owner) { owner.UnassignHolder(); }

		~UnassignGuard() { owner_.AssignHolder(); }

	private:
		MutexLock& owner_;
	};

	void UnassignHolder() { holder_ = 0; }

	void AssignHolder() { holder_ = current_thread::Tid(); }

	pthread_mutex_t mutex_;
	pid_t holder_{0};
};

// Use as a stack variable, eg.
// int Foo::size() const
// {
//   MutexLockGuard lock(mutex_);
//   return data_.size();
// }
class SCOPED_CAPABILITY MutexLockGuard : NonCopyable {
public:
	explicit MutexLockGuard(MutexLock& mutex) ACQUIRE(mutex) : mutex_(mutex) { mutex.Lock(); }

	~MutexLockGuard() RELEASE() { mutex_.Unlock(); }

private:
	MutexLock& mutex_;
};

} // namespace netlib

// Prevent misuse like:
// MutexLockGuard(mutex_);
// A tempory object doesn't hold the lock for long!
#define MutexLockGuard(x) error "Missing guard object name"

#endif // netlib_BASE_MUTEX_H
